JE prépare ma session de travail

setwd("~/GitHub/Cours_2020_UniGE/Cours_Geneve_6")

Je charge les packages utiles

if(!require("stylo")){
  install.packages("stylo")
  library(stylo)
}
if(!require("cluster")){
  install.packages("cluster")
  library(cluster)
}
if(!require("FactoMineR")){
  install.packages("FactoMineR")
  library(FactoMineR)
}
if(!require("factoextra")){
  install.packages("factoextra")
  library(factoextra)
}
if(!require("smacof")){
  install.packages("smacof")
  library(smacof)
}
Loading required package: smacof
Loading required package: plotrix
Registered S3 methods overwritten by 'car':
  method                          from
  influence.merMod                lme4
  cooks.distance.influence.merMod lme4
  dfbeta.influence.merMod         lme4
  dfbetas.influence.merMod        lme4

Attaching package: ‘smacof’

The following object is masked from ‘package:base’:

    transform

Ma première CAH depuis 0

Une classification ascendante hiérarchique.

# Je crée un tableau avec des données au hasard
tableau <- cbind(x=c(1,2,5,8,11),y=c(2,4,1,12,10))
colnames(tableau)<- c("Avoir", "Être")
rownames(tableau) <- c("Racine","Molière","Racine","Scarron","Corneille")
tableau
          Avoir Être
Racine        1    2
Molière       2    4
Racine        5    1
Scarron       8   12
Corneille    11   10

Calcul de distance, par défaut eucidienne. on peut utiliser le paramètre method=“manhattan” pour une autre distance

distance_tableau <- dist(tableau)
distance_tableau
             Racine   Molière    Racine   Scarron
Molière    2.236068                              
Racine     4.123106  4.242641                    
Scarron   12.206556 10.000000 11.401754          
Corneille 12.806248 10.816654 10.816654  3.605551

Classification/clustering. Par défaut complete linkage. On peut utiliser le paramètre method=“centroid” ou “ward.D”

hc <- hclust(distance_tableau)
plot(hc)

#On customise le graph
plot(hc, main="Ma première classification",  xlab="Euclidian distance, \n Complete-linkage clustering" , sub="" , hang =-1)

On peut faire soi-même un delta de Burrows. Pour cela je z-score mes données

#Je transforme mon tableau en data.frame
tableau_df<-as.data.frame(tableau)
#Je z-score mes deux colonnes avec la fonction scale()
tableau_z_a<-scale(tableau_df$Avoir)
tableau_z_b<-scale(tableau_df$Être)
#Je reforme un tableau, avec les nouvelles données
tableau_z<-cbind(tableau_z_a,tableau_z_b)
tableau_z
            [,1]       [,2]
[1,] -1.05786348 -0.7724598
[2,] -0.81743996 -0.3659020
[3,] -0.09616941 -0.9757388
[4,]  0.62510115  1.2603292
[5,]  1.34637170  0.8537714

Et je produis un nouveau graph

hc <- hclust(distance_tableau, method="ward.D")
plot(hc, main="Ma première classificatoin",  xlab="Burrows Delta, \n Ward's method" , sub="" , hang =-1)

Un premier cas

Je charge mon corpus

BOYER_AMOURSJUPITERSEMELE_1666<-paste(scan("corpus/BOYER_AMOURSJUPITERSEMELE_1666.txt", what="character", sep=""),collapse=" ")
Read 17734 items
BOYER_ARISTODEME_1648<-paste(scan("corpus/BOYER_ARISTODEME_1648.txt", what="character", sep=""),collapse=" ")
Read 11106 items
CORNEILLEP_ANDROMEDE_1651<-paste(scan("corpus/CORNEILLEP_ANDROMEDE_1651.txt", what="character", sep=""),collapse=" ")
Read 16121 items
CORNEILLEP_MEDEE_1682<-paste(scan("corpus/CORNEILLEP_MEDEE_1682.txt", what="character", sep=""),collapse=" ")
Read 13705 items
DURYER_DYNAMIS_1653<-paste(scan("corpus/DURYER_DYNAMIS_1653.txt", what="character", sep=""),collapse=" ")
Read 16793 items
DURYER_CLITOPHON<-paste(scan("corpus/DURYER_CLITOPHON_1632.txt", what="character", sep=""),collapse=" ")
Read 15265 items
MOLIERE_AMPHITRYON_1668<-paste(scan("corpus/MOLIERE_AMPHITRYON_1668.txt", what="character", sep=""),collapse=" ")
Read 14152 items
MOLIERE_MISANTHROPE_1667<-paste(scan("corpus/MOLIERE_MISANTHROPE_1667.txt", what="character", sep=""),collapse=" ")
Read 16074 items
RACINE_PHEDRE_1697<-paste(scan("corpus/RACINE_PHEDRE_1697.txt", what="character", sep=""),collapse=" ")
Read 13434 items
RACINE_ESTHER_1689<-paste(scan("corpus/RACINE_ESTHER_1689.txt", what="character", sep=""),collapse=" ")
Read 10460 items
SCARRON_DOMJAPHETDARMENIE_1653<-paste(scan("corpus/SCARRON_DOMJAPHETDARMENIE_1653.txt", what="character", sep=""),collapse=" ")
Read 12945 items
SCARRON_ECOLIERDESALAMANQUE_1655<-paste(scan("corpus/SCARRON_ECOLIERDESALAMANQUE_1655.txt", what="character", sep=""),collapse=" ")
Read 14133 items
ROTROU_HERCULEMOURANT_1636<-paste(scan("corpus/ROTROU_HERCULEMOURANT_1636.txt", what="character", sep=""),collapse=" ")
Read 12561 items
ROTROU_DOMBERNARDDECABRERE_1647<-paste(scan("corpus/ROTROU_DOMBERNARDDECABRERE_1647.txt", what="character", sep=""),collapse=" ")
Read 14546 items
SCUDERY_MORTDECESAR_1637<-paste(scan("corpus/SCUDERY_MORTDECESAR_1637.txt", what="character", sep=""),collapse=" ")
Read 11918 items
SCUDERY_ORANTE_1636<-paste(scan("corpus/SCUDERY_ORANTE_1636.txt", what="character", sep=""),collapse=" ")
Read 12408 items
#Je peux regarder ici un exemple (enlever le # au début de la ligne)
#CORNEILLEP_ANDROMEDE_1651

J’en fais un tableau

#Je crée une liste de mes textes
my.corpus.raw = list(BOYER_AMOURSJUPITERSEMELE_1666,
                     BOYER_ARISTODEME_1648,
                     DURYER_DYNAMIS_1653,
                     DURYER_CLITOPHON,
                     MOLIERE_AMPHITRYON_1668,
                     MOLIERE_MISANTHROPE_1667,
                     SCARRON_DOMJAPHETDARMENIE_1653,
                     SCARRON_ECOLIERDESALAMANQUE_1655)
#Je tokenise mon texte (espace, apostrophe, retrait de la ponctuation)
my.corpus.clean = lapply(my.corpus.raw, txt.to.words)
#Je compte la fréquence des tokens
complete.word.list = make.frequency.list(my.corpus.clean)
#Je transforme le résultat en table, où les résultats de chaque mot/texte sont alignés
table.of.frequencies=make.table.of.frequencies(my.corpus.clean, complete.word.list, relative = F)
processing  8  text samples
       
combining frequencies into a table...
#Je donne un nom à mes colonnes
row.names(table.of.frequencies)=c("boyer_agamemnon",
                                  "boyer_amoursJupiter",
                                  "duryer_dynamis",
                                  "duryer_clitophon",
                                  "moliere_amphytrion",
                                  "moliere_misanthrope",
                                  "scarron_domJaphet",
                                  "scarron_ecolierSalamanque")
#Je sauvegarde une copie
write.csv(table.of.frequencies, file = "table.of.frequencies.csv",row.names=TRUE)
#je convertis mes données en dataframe
table.of.frequencies = as.data.frame(read.csv(file="table.of.frequencies.csv", sep = ",", header = TRUE, row.names=1, quote = '\"'))
#J'affiche le résultat
#View(table.of.frequencies)

Ce résultat ne m’arrange pas: je vais avoir besoin d’avoir les occurrences en rang, et les textes en colonne, et donc d’inverser mon tableau

#j'utilise la fonction transpose(?)
table.of.frequencies<-t(table.of.frequencies)
#View(table.of.frequencies)

J’observe la distribution de mon corpus.

summary(table.of.frequencies)
 boyer_agamemnon   boyer_amoursJupiter duryer_dynamis    duryer_clitophon moliere_amphytrion
 Min.   :  0.000   Min.   :  0.000     Min.   :  0.000   Min.   :  0.00   Min.   :  0.000   
 1st Qu.:  0.000   1st Qu.:  0.000     1st Qu.:  0.000   1st Qu.:  0.00   1st Qu.:  0.000   
 Median :  0.000   Median :  0.000     Median :  0.000   Median :  0.00   Median :  0.000   
 Mean   :  2.208   Mean   :  1.425     Mean   :  2.127   Mean   :  1.92   Mean   :  1.783   
 3rd Qu.:  1.000   3rd Qu.:  0.000     3rd Qu.:  1.000   3rd Qu.:  1.00   3rd Qu.:  1.000   
 Max.   :599.000   Max.   :321.000     Max.   :589.000   Max.   :537.00   Max.   :478.000   
 moliere_misanthrope scarron_domJaphet scarron_ecolierSalamanque
 Min.   :  0.000     Min.   :  0.000   Min.   :  0.000          
 1st Qu.:  0.000     1st Qu.:  0.000   1st Qu.:  0.000          
 Median :  0.000     Median :  0.000   Median :  0.000          
 Mean   :  2.018     Mean   :  1.686   Mean   :  1.828          
 3rd Qu.:  1.000     3rd Qu.:  1.000   3rd Qu.:  1.000          
 Max.   :567.000     Max.   :425.000   Max.   :423.000          

Je peux synthétiser ce résultat avec une boîte à moustache des fréquences (absolues) des mots du corpus.

100% center

100% center

Source: stat4decision

maBoite <- boxplot(colSums(table.of.frequencies), main = "Distribution du nombre de mots par de texte", ylab="Nombre de mots (fréq. absolues)", sub="Corpus: distribution")

Je contrôle l’hétérogénéité de mon corpus

#Je dois regrouper les données par auteur
BOYER = colSums(table.of.frequencies[,grepl('boyer_', colnames(table.of.frequencies))])
CORNEILLE = colSums(table.of.frequencies[,grepl('corneille_', colnames(table.of.frequencies))])
DURYER = colSums(table.of.frequencies[,grepl('duryer_', colnames(table.of.frequencies))])
MOLIERE = colSums(table.of.frequencies[,grepl('moliere_', colnames(table.of.frequencies))])
RACINE = colSums(table.of.frequencies[,grepl('racine_', colnames(table.of.frequencies))])
SCARRON = colSums(table.of.frequencies[,grepl('scarron_', colnames(table.of.frequencies))])
ROTROU = colSums(table.of.frequencies[,grepl('rotrou_', colnames(table.of.frequencies))])
#Et le graphique
boxplot(list(BOYER,MOLIERE, CORNEILLE,DURYER,RACINE,SCARRON, ROTROU), names=c('BOYER','MOLIERE', 'CORNEILLE','DURYER','RACINE','SCARRON','ROTROU'), main="Longueur des textes", sub="Corpus: distribution",ylab="Nombre de mots (fréquences absolues)")

Je transforme cette table des fréquences absolues en une table des fréquences relatives

#Je fais une copie de ma table de fréquences
freqs_rel = table.of.frequencies
#Dans cette copie, pour chacun des mots de chaque colonne, je divise le chiffre trouvé par la somme de la colonne
for(i in 1:ncol(freqs_rel)){
        freqs_rel[,i] = freqs_rel[,i]/sum(freqs_rel[,i])
}
head(freqs_rel)
     boyer_agamemnon boyer_amoursJupiter duryer_dynamis duryer_clitophon moliere_amphytrion
de        0.03211280          0.02666556     0.02459929       0.03310319         0.03173339
et        0.02728784          0.02475494     0.03278050       0.02404143         0.02701985
je        0.01613681          0.01918923     0.01775378       0.01658242         0.02243909
vous      0.02299898          0.01844160     0.01703028       0.00961657         0.01566753
que       0.01704820          0.01910616     0.02276269       0.02416471         0.02077939
le        0.01715542          0.01702941     0.02075913       0.01713722         0.01427339
     moliere_misanthrope scarron_domJaphet scarron_ecolierSalamanque
de            0.03162403        0.02984131                0.02738573
et            0.03150669        0.02260918                0.02732099
je            0.02434874        0.02569864                0.02596141
vous          0.03326684        0.02155596                0.02537874
que           0.02018306        0.01530684                0.02026415
le            0.01513729        0.01987080                0.01573223

Je peux (et même je dois) sélectionner dans cette liste les n plus fréquents

#Je prends les premiers rangs. Changez ce chiffre pour changer les résultats par la suite
freqs_rel_mfw = freqs_rel[1:100,]

Analyse

La CAH

Regardons à quoi ressemble notre CAH

CAH = agnes(as.dist(dist.wurzburg(t(freqs_rel_mfw))), method="ward")
CAH_orig_aggloCoeff <- CAH$ac
plot(CAH)

Nettoyons tout cela…

plot(CAH,
     main="Cluster analysis",
     xlab=paste("Cosine Delta\n Agglomerative coefficient =", CAH_orig_aggloCoeff),
     hang = -0.1)

Faisons un peu mieux

#on va colorier les vecteurs en fonction de l'auteur (en pratique, la chaîne de caractère avant l'_underscore_)
labels_color = vector(length = length(CAH$order.lab))
labels_color[grep("boyer", CAH$order.lab)] = "darkblue"
labels_color[grep("duryer", CAH$order.lab)] = "darkgrey"
labels_color[grep("scarron", CAH$order.lab)] = "darkred"
labels_color[grep("moliere", CAH$order.lab)] = "darkgreen"
#je reprends mon graph
plot(CAH)

#Je fais un peu de coloriage
factoextra::fviz_dend(CAH,
                      ylab=paste("Cosine Delta\n Agglomerative coefficient =", round(CAH_orig_aggloCoeff, digit=3)),
                      k=2, # je cherche à dégager deux clusters
                      rect = TRUE,
                      k_colors = rep("black",2), #je garde la couleur des traits en noir
                      labels_track_height = 0.7, # Je gère la taille de mon rectangle
                      label_cols = labels_color, #j'applique mes couleurs
                      horiz=T) # je mets mon graph à l'horizontal, parce que c'est plus pratique

C’est bien mieux, mais pas encore parfait: le code est est trop long, pas très propre. Cachons tout cela dans un coin. Pour cela, compliquons-nous le travail un peu plus en créant une fonction je que conserve dans un autre fichier appelé functions.R.

Je vais donc charger cette fonction avant de l’utiliser.

source("functions.R")

Quatre paramètres ont été prévus * Le nom des données * La légende à afficher * Le coeffichient d’agglomération (récupéré au passage, un peu plus haut) * une variable pour le paramètre labels_track_height

customPlot(CAH,"Original texts, 100 MFW, Culled @ 0%, Distance: wurzburg",CAH_orig_aggloCoeff,0.7)

Je peux donc reproduire ma CAH simplement avec d’autres configurations. Tentons désormais avec plus de MFW

#on passe de 100 à 1000
freqs_rel_mfw = freqs_rel[1:1000,]
#je recalcule ma CAH
CAH = agnes(as.dist(dist.wurzburg(t(freqs_rel_mfw))), method="ward")
CAH_orig_aggloCoeff <- CAH$ac
#Et désormais, je n'ai plus qu'une simple fonction "maison" pour me produire ma visualisation
customPlot(CAH,"Original texts, 1000 MFW, Culled @ 0%, Distance: wurzburg",CAH_orig_aggloCoeff,0.5)

Tentons avec une autre distance: Ruzicka measure ou Minmax (cf.Koppel, M. and Winter, Y. (2014). Determining if two documents are written by the same author. Journal of the Association for Information Science and Technology, 65: 178–87) dont la robustesse a été démontrée par Kestmont et al. ( Kestemont, M.Stover, J.Koppel, M.Karsdorp, K.Daelemans, W., Authorship Verification with the Ruzicka Metric, DH 2016, Jul 2016, Krakow (Poland)).

100% center

100% center

(tf pour term frequency)

#je recalcule ma CAH
CAH = agnes(as.dist(dist.minmax(t(freqs_rel_mfw))), method="ward")
CAH_orig_aggloCoeff <- CAH$ac
#Et désormais, je n'ai plus qu'une simple fonction "maison" pour me produire ma visualisation
customPlot(CAH,"Original texts, 1000 MFW, Culled @ 0%, Distance: Minmax",CAH_orig_aggloCoeff,-0.2)

Et encore une autre, en diminuant de nouveau les MFW

#on repasse de 1000 à 100
freqs_rel_mfw = freqs_rel[1:100,]
#je recalcule ma CAH
CAH = agnes(as.dist(dist.wurzburg(t(freqs_rel_mfw))), method="ward")
CAH_orig_aggloCoeff <- CAH$ac
customPlot(CAH,"Original texts, 100 MFW, Culled @ 0%, Distance: wurzburg",CAH_orig_aggloCoeff,0.7)

Under the hood: les classes

Tout cela est très bien. Mais qu’est-ce qui est caché derrière ces clusters? Nous pouvons le savoir. Prenons notre dernière CAH, et regardons le processus de construction des clusters:

CAH_2 = as.hclust(CAH)
plot(CAH_2$height, type="h", ylab="height")

Je remarque que j’ai plusieurs clusters. Tentons de voir ce qu’ils contiennent. Classons par 4 notre corpus:

#Je "coupe en deux" ma CAH, ou plutôt je demande une classification en deux des différents
classes = cutree(CAH, k = "4")
#J'ajoute mes classes aux fréquences que j'ai utilisées
Processed_classes = t(freqs_rel_mfw)
Processed_classes = cbind(as.data.frame(Processed_classes), as.factor(classes))
colnames(Processed_classes)[101] = "Classes"
#J'affiche le résultat
Processed_classes[101]
                          Classes
boyer_agamemnon                 1
boyer_amoursJupiter             1
duryer_dynamis                  2
duryer_clitophon                2
moliere_amphytrion              3
moliere_misanthrope             3
scarron_domJaphet               4
scarron_ecolierSalamanque       4

Diminuons à deux le nombre de cluster

#Je "coupe en deux" ma CAH, ou plutôt je demande une classification en deux des différents
classes = cutree(CAH, k = "2")
#J'ajoute mes classes aux fréquences que j'ai utilisées
Processed_classes = t(freqs_rel_mfw)
Processed_classes = cbind(as.data.frame(Processed_classes), as.factor(classes))
colnames(Processed_classes)[101] = "Classes"
#J'affiche le résultat
Processed_classes[101]
                          Classes
boyer_agamemnon                 1
boyer_amoursJupiter             1
duryer_dynamis                  1
duryer_clitophon                1
moliere_amphytrion              2
moliere_misanthrope             2
scarron_domJaphet               2
scarron_ecolierSalamanque       2

Je peux aussi regarder les valeurs associées à chacun des mots, et ceux associés à chaque cluster:

myClasses = catdes(Processed_classes, num.var = 101)
myClasses

Link between the cluster variable and the quantitative variables
================================================================
           Eta2      P-value
je    0.8855821 0.0004896757
amour 0.8040975 0.0025455982
yeux  0.7601916 0.0047643342
moi   0.7512318 0.0053410502
y     0.7445267 0.0058031317
pas   0.7375033 0.0063162127
être  0.7371581 0.0063422162
n     0.7356576 0.0064561179
si    0.7193183 0.0077904004
l     0.7138624 0.0082757444
ne    0.6959504 0.0100182228
faire 0.6770794 0.0121175393
dieux 0.6744536 0.0124323540
qui   0.6573420 0.0146275157
mes   0.6342385 0.0180111247
en    0.6330403 0.0182005359
veux  0.6181419 0.0206765720
m     0.6095886 0.0222026475
ses   0.5960834 0.0247753466
plus  0.5890029 0.0262070694
suis  0.5741345 0.0294079634
comme 0.5457179 0.0363077270
bien  0.5423877 0.0371876525
me    0.5098090 0.0466527660
point 0.5027205 0.0489296469

Description of each cluster by quantitative variables
=====================================================
$`1`
         v.test Mean in category Overall mean sd in category   Overall sd    p.value
amour  2.372484      0.004818126  0.003380804   0.0008299353 0.0016028752 0.01766891
yeux   2.306803      0.001808802  0.001422762   0.0001256128 0.0004427618 0.02106579
si     2.243931      0.009360696  0.007472486   0.0015781275 0.0022263300 0.02483681
l      2.235405      0.017151679  0.015865132   0.0009675451 0.0015227141 0.02539075
dieux  2.172826      0.003528194  0.001978623   0.0014225437 0.0018868420 0.02979338
qui    2.145086      0.009527600  0.008502894   0.0005062741 0.0012638733 0.03194597
mes    2.107052      0.003316553  0.002528891   0.0007548847 0.0009890388 0.03511304
ses    2.042690      0.002753466  0.001970091   0.0007580942 0.0010146507 0.04108310
plus   2.030522      0.005931663  0.005243258   0.0006308718 0.0008969846 0.04230349
suis  -2.004730      0.001103213  0.002013334   0.0005036370 0.0012011368 0.04499194
m     -2.065701      0.004543850  0.005795718   0.0008642927 0.0016033934 0.03885672
veux  -2.080143      0.001166075  0.001530247   0.0001085254 0.0004631936 0.03751245
en    -2.105061      0.010588157  0.012344905   0.0015522742 0.0022079724 0.03528599
faire -2.177052      0.001783577  0.002343016   0.0004299235 0.0006798804 0.02947669
ne    -2.207182      0.008047953  0.009833099   0.0010665234 0.0021398561 0.02730134
n     -2.269274      0.005291597  0.006619240   0.0008650303 0.0015479011 0.02325169
être  -2.271587      0.001702773  0.002472563   0.0003633443 0.0008965862 0.02311147
pas   -2.272119      0.004991906  0.006090712   0.0006925025 0.0012794955 0.02307934
y     -2.282912      0.001141360  0.001708866   0.0003815097 0.0006577039 0.02243556
moi   -2.293169      0.003940317  0.005481745   0.0003737930 0.0017784282 0.02183829
je    -2.489794      0.017415563  0.021013767   0.0011821199 0.0038235906 0.01278171

$`2`
         v.test Mean in category Overall mean sd in category   Overall sd    p.value
je     2.489794     0.0246119713  0.021013767   0.0013957565 0.0038235906 0.01278171
moi    2.293169     0.0070231732  0.005481745   0.0011974515 0.0017784282 0.02183829
y      2.282912     0.0022763722  0.001708866   0.0002747232 0.0006577039 0.02243556
pas    2.272119     0.0071895170  0.006090712   0.0006163696 0.0012794955 0.02307934
être   2.271587     0.0032423534  0.002472563   0.0005390367 0.0008965862 0.02311147
n      2.269274     0.0079468818  0.006619240   0.0007200349 0.0015479011 0.02325169
ne     2.207182     0.0116182445  0.009833099   0.0012833566 0.0021398561 0.02730134
faire  2.177052     0.0029024541  0.002343016   0.0003371910 0.0006798804 0.02947669
en     2.105061     0.0141016520  0.012344905   0.0010809285 0.0022079724 0.03528599
veux   2.080143     0.0018944192  0.001530247   0.0003899695 0.0004631936 0.03751245
m      2.065701     0.0070475865  0.005795718   0.0011226719 0.0016033934 0.03885672
suis   2.004730     0.0029234554  0.002013334   0.0009875056 0.0012011368 0.04499194
plus  -2.030522     0.0045548539  0.005243258   0.0005131879 0.0008969846 0.04230349
ses   -2.042690     0.0011867150  0.001970091   0.0005069224 0.0010146507 0.04108310
mes   -2.107052     0.0017412296  0.002528891   0.0003817371 0.0009890388 0.03511304
qui   -2.145086     0.0074781878  0.008502894   0.0009156383 0.0012638733 0.03194597
dieux -2.172826     0.0004290517  0.001978623   0.0005425611 0.0018868420 0.02979338
l     -2.235405     0.0145785853  0.015865132   0.0006251135 0.0015227141 0.02539075
si    -2.243931     0.0055842772  0.007472486   0.0005403116 0.0022263300 0.02483681
yeux  -2.306803     0.0010367229  0.001422762   0.0002797223 0.0004427618 0.02106579
amour -2.372484     0.0019434819  0.003380804   0.0005637698 0.0016028752 0.01766891

Under the hood: l’es classes’ACP

Je commence par faire une ACP.

ACP = PCA(t(freqs_rel_mfw))

Faisons un peu de ménage, ajoutons de l’information, et tentons de comprendre

#categories
get_categories = vector(length = length(colnames(freqs_rel_mfw)))
get_categories[grep("racine", colnames(freqs_rel_mfw))] = "Racine"
get_categories[grep("moliere", colnames(freqs_rel_mfw))] = "Moliere"
get_categories[grep("duryer", colnames(freqs_rel_mfw))] = "Duryer"
get_categories[grep("boyer", colnames(freqs_rel_mfw))] = "Boyer"
ACP = PCA(t(freqs_rel_mfw))

fviz_pca_ind(ACP, col.ind = get_categories, legend="none")

On voit nettement deux axes, qu doivent bien s’appuyer sur quelque chose. Serait-il possible de faire ressortir les mots qui sont à l’origine de ce partitionnement des données?

Continuons notre nettoyage pour y voir plus clair

#categories
get_categories = vector(length = length(colnames(freqs_rel_mfw)))
get_categories[grep("racine", colnames(freqs_rel_mfw))] = "Racine"
get_categories[grep("moliere", colnames(freqs_rel_mfw))] = "Moliere"
ACP = PCA(t(freqs_rel_mfw))

fviz_pca_ind(ACP, col.ind = get_categories, legend="none")

fviz_pca_var(ACP, col.var="contrib",geom.var = "text", select.var = list(contrib =20))+
scale_color_gradient2(
                      low="yellow", mid="orange", 
                      high="darkred", midpoint=1.6)+theme_bw()

Nous nous rappelons qu’il existe un pourcentage de réalité retenu par axe, que nous pouvons observer, et donc calculer. Dans ce cas précis, il y en a seulement trois axes, car nous n’avons mis que quatre de textes.

ACP$eig
       eigenvalue percentage of variance cumulative percentage of variance
comp 1  30.037372              30.037372                          30.03737
comp 2  21.041028              21.041028                          51.07840
comp 3  15.055366              15.055366                          66.13377
comp 4  11.101088              11.101088                          77.23485
comp 5   9.201767               9.201767                          86.43662
comp 6   7.811868               7.811868                          94.24849
comp 7   5.751511               5.751511                         100.00000

Un petit graph montre bien la lente perte de significativité des axes :

barplot(ACP$eig[,1], main="Percentage of variance", names.arg=1:nrow(ACP$eig))

Les autres algorithmes

t-SNE

Avec l’algorithme de Barnes-Hut,

library(Rtsne)
maRtsne = Rtsne(t(freqs_rel_mfw), dims = 2, initial_dims = 36, perplexity = 0.5, theta = 0.0, check_duplicates = TRUE, pca = TRUE)
plot(maRtsne$Y)
text(maRtsne$Y[,1], maRtsne$Y[,2], labels = row.names(t(freqs_rel_mfw)), cex=.6) 

Attention! Si on relance la même commande, on obtient un résultat différent!

library(Rtsne)
maRtsne = Rtsne(t(freqs_rel_mfw), dims = 2, initial_dims = 36, perplexity = 0.5, theta = 0.0, check_duplicates = TRUE, pca = TRUE)
plot(maRtsne$Y)
text(maRtsne$Y[,1], maRtsne$Y[,2], labels = row.names(t(freqs_rel_mfw)), cex=.6) 

#encore une fois
library(Rtsne)
maRtsne = Rtsne(t(freqs_rel_mfw), dims = 2, initial_dims = 36, perplexity = 0.5, theta = 0.0, check_duplicates = TRUE, pca = TRUE)
plot(maRtsne$Y)
text(maRtsne$Y[,1], maRtsne$Y[,2], labels = row.names(t(freqs_rel_mfw)), cex=.6) 

#encore une fois
library(Rtsne)
maRtsne = Rtsne(t(freqs_rel_mfw), dims = 2, initial_dims = 36, perplexity = 0.5, theta = 0.0, check_duplicates = TRUE, pca = TRUE)
plot(maRtsne$Y)
text(maRtsne$Y[,1], maRtsne$Y[,2], labels = row.names(t(freqs_rel_mfw)), cex=.6) 

On peut faire varier la perpléxité (c’est à dire s’autoriser une distorsion plus grande): on passe à 1.5

maRtsne = Rtsne(t(freqs_rel_mfw), dims = 2, initial_dims = 36, perplexity = 1.5, theta = 0.0, check_duplicates = TRUE, pca = TRUE)
plot(maRtsne$Y)
text(maRtsne$Y[,1], maRtsne$Y[,2], labels = row.names(t(freqs_rel_mfw)), cex=.6) 

#Encore une fois
maRtsne = Rtsne(t(freqs_rel_mfw), dims = 2, initial_dims = 36, perplexity = 1.5, theta = 0.0, check_duplicates = TRUE, pca = TRUE)
plot(maRtsne$Y)
text(maRtsne$Y[,1], maRtsne$Y[,2], labels = row.names(t(freqs_rel_mfw)), cex=.6) 

#Encore une fois
maRtsne = Rtsne(t(freqs_rel_mfw), dims = 2, initial_dims = 36, perplexity = 1.5, theta = 0.0, check_duplicates = TRUE, pca = TRUE)
plot(maRtsne$Y)
text(maRtsne$Y[,1], maRtsne$Y[,2], labels = row.names(t(freqs_rel_mfw)), cex=.6) 

#Encore une fois
maRtsne = Rtsne(t(freqs_rel_mfw), dims = 2, initial_dims = 36, perplexity = 1.5, theta = 0.0, check_duplicates = TRUE, pca = TRUE)
plot(maRtsne$Y)
text(maRtsne$Y[,1], maRtsne$Y[,2], labels = row.names(t(freqs_rel_mfw)), cex=.6) 

On peut faire varier la perpléxité: on passe à 2

maRtsne = Rtsne(t(freqs_rel_mfw), dims = 2, initial_dims = 36, perplexity = 2, theta = 0.0, check_duplicates = TRUE, pca = TRUE)
plot(maRtsne$Y)
text(maRtsne$Y[,1], maRtsne$Y[,2], labels = row.names(t(freqs_rel_mfw)), cex=.6) 

#Encore une fois
maRtsne = Rtsne(t(freqs_rel_mfw), dims = 2, initial_dims = 36, perplexity = 2, theta = 0.0, check_duplicates = TRUE, pca = TRUE)
plot(maRtsne$Y)
text(maRtsne$Y[,1], maRtsne$Y[,2], labels = row.names(t(freqs_rel_mfw)), cex=.6) 

#Encore une fois
maRtsne = Rtsne(t(freqs_rel_mfw), dims = 2, initial_dims = 36, perplexity = 2, theta = 0.0, check_duplicates = TRUE, pca = TRUE)
plot(maRtsne$Y)
text(maRtsne$Y[,1], maRtsne$Y[,2], labels = row.names(t(freqs_rel_mfw)), cex=.6) 

#Encore une fois
maRtsne = Rtsne(t(freqs_rel_mfw), dims = 2, initial_dims = 36, perplexity = 2, theta = 0.0, check_duplicates = TRUE, pca = TRUE)
plot(maRtsne$Y)
text(maRtsne$Y[,1], maRtsne$Y[,2], labels = row.names(t(freqs_rel_mfw)), cex=.6) 

MDS

Le Multidimensional scaling est une compression des données en deux dimensions. Il en existe trois types: * classique * métrique * non métrique

MDS classique

Le MDS classique se base sur le calcul de distance, dont il va essayer de préserver l’essentiel.

#Je crée mes données
fit = cmdscale(dist(t(freqs_rel_mfw), method = "manhattan"), eig=TRUE, k=2) # k est le nombre de dimensions souhaité
#Je dessine mon résultat
x = fit$points[,1]
y = fit$points[,2]
plot(x, y, xlab=paste("Coordinate 1 (GOF: ", round(fit$GOF[1] * 100, digits=2), "%)"), ylab=paste("Coordinate 2 (GOF: ", round(fit$GOF[2] * 100, digits=2), "%)"), main="PMD métrique")
text(x, y, labels = row.names(t(freqs_rel_mfw)), cex=.7) 

MDS Métrique

Le MDS métrique, dit aussi ordinal, ne s’intéresse pas à la mesure de distance, mais sa valeur en relation avec les autres paires

Rappelons que le stress permet de mesurer la distortion introduite dans le résultat.

#J'affiche le stress avec le paramètre verbose=T(RUE)
MDSmetrique = mds(dist(t(freqs_rel_mfw), method = "manhattan"), ndim=2, type="ordinal",verbose=T)
Iteration:    1  Stress (raw):   0.01510422  Difference:    0.06233185 
Iteration:    2  Stress (raw):   0.00887573  Difference:    0.00622848 
Iteration:    3  Stress (raw):   0.00656184  Difference:    0.00231389 
Iteration:    4  Stress (raw):   0.00541200  Difference:    0.00114984 
Iteration:    5  Stress (raw):   0.00475869  Difference:    0.00065331 
Iteration:    6  Stress (raw):   0.00439233  Difference:    0.00036636 
Iteration:    7  Stress (raw):   0.00417426  Difference:    0.00021807 
Iteration:    8  Stress (raw):   0.00403217  Difference:    0.00014209 
Iteration:    9  Stress (raw):   0.00393218  Difference:    0.00009999 
Iteration:   10  Stress (raw):   0.00385734  Difference:    0.00007485 
Iteration:   11  Stress (raw):   0.00379958  Difference:    0.00005776 
Iteration:   12  Stress (raw):   0.00375696  Difference:    0.00004262 
Iteration:   13  Stress (raw):   0.00372424  Difference:    0.00003271 
Iteration:   14  Stress (raw):   0.00369790  Difference:    0.00002634 
Iteration:   15  Stress (raw):   0.00367617  Difference:    0.00002173 
Iteration:   16  Stress (raw):   0.00365798  Difference:    0.00001819 
Iteration:   17  Stress (raw):   0.00364262  Difference:    0.00001537 
Iteration:   18  Stress (raw):   0.00362954  Difference:    0.00001308 
Iteration:   19  Stress (raw):   0.00361836  Difference:    0.00001119 
Iteration:   20  Stress (raw):   0.00360875  Difference:    0.00000961 
Iteration:   21  Stress (raw):   0.00360047  Difference:    0.00000828 
Iteration:   22  Stress (raw):   0.00359331  Difference:    0.00000716 
Iteration:   23  Stress (raw):   0.00358711  Difference:    0.00000620 
Iteration:   24  Stress (raw):   0.00358173  Difference:    0.00000538 
Iteration:   25  Stress (raw):   0.00357706  Difference:    0.00000467 
Iteration:   26  Stress (raw):   0.00357299  Difference:    0.00000407 
Iteration:   27  Stress (raw):   0.00356944  Difference:    0.00000355 
Iteration:   28  Stress (raw):   0.00356633  Difference:    0.00000311 
Iteration:   29  Stress (raw):   0.00356361  Difference:    0.00000272 
Iteration:   30  Stress (raw):   0.00356122  Difference:    0.00000238 
Iteration:   31  Stress (raw):   0.00355913  Difference:    0.00000209 
Iteration:   32  Stress (raw):   0.00355730  Difference:    0.00000184 
Iteration:   33  Stress (raw):   0.00355568  Difference:    0.00000161 
Iteration:   34  Stress (raw):   0.00355427  Difference:    0.00000142 
Iteration:   35  Stress (raw):   0.00355302  Difference:    0.00000125 
Iteration:   36  Stress (raw):   0.00355193  Difference:    0.00000110 
Iteration:   37  Stress (raw):   0.00355096  Difference:    0.00000096 
plot(MDSmetrique, sub=paste("Stress, ", round(MDSmetrique$stress, digits=2)))

Non métrique

#J'affiche le stress avec le paramètre verbose=T(RUE)
MDSnonMetrique = mds(dist(t(freqs_rel_mfw), method = "euclid"), ndim=2, type="interval",verbose=T)
Iteration:    1  Stress (raw):   0.02349141  Difference:    0.05037561 
Iteration:    2  Stress (raw):   0.01787966  Difference:    0.00561176 
Iteration:    3  Stress (raw):   0.01617650  Difference:    0.00170315 
Iteration:    4  Stress (raw):   0.01541740  Difference:    0.00075910 
Iteration:    5  Stress (raw):   0.01499855  Difference:    0.00041885 
Iteration:    6  Stress (raw):   0.01474414  Difference:    0.00025441 
Iteration:    7  Stress (raw):   0.01458330  Difference:    0.00016085 
Iteration:    8  Stress (raw):   0.01447937  Difference:    0.00010393 
Iteration:    9  Stress (raw):   0.01441109  Difference:    0.00006828 
Iteration:   10  Stress (raw):   0.01436553  Difference:    0.00004556 
Iteration:   11  Stress (raw):   0.01433468  Difference:    0.00003085 
Iteration:   12  Stress (raw):   0.01431351  Difference:    0.00002117 
Iteration:   13  Stress (raw):   0.01429880  Difference:    0.00001471 
Iteration:   14  Stress (raw):   0.01428848  Difference:    0.00001033 
Iteration:   15  Stress (raw):   0.01428115  Difference:    0.00000732 
Iteration:   16  Stress (raw):   0.01427593  Difference:    0.00000523 
Iteration:   17  Stress (raw):   0.01427217  Difference:    0.00000376 
Iteration:   18  Stress (raw):   0.01426945  Difference:    0.00000272 
Iteration:   19  Stress (raw):   0.01426747  Difference:    0.00000197 
Iteration:   20  Stress (raw):   0.01426603  Difference:    0.00000144 
Iteration:   21  Stress (raw):   0.01426498  Difference:    0.00000105 
Iteration:   22  Stress (raw):   0.01426421  Difference:    0.00000077 
plot(MDSnonMetrique, sub=paste("Stress, ", round(MDSmetrique$stress, digits=2)))

LS0tCnRpdGxlOiAiQ291cnNfR2VuZXZlXzYiCmF1dGhvcjogIlNpbW9uIEdhYmF5IgpkYXRlOiAiMTcvMDMvMjAyMCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCkpFIHByw6lwYXJlIG1hIHNlc3Npb24gZGUgdHJhdmFpbAoKYGBge3J9CnNldHdkKCJ+L0dpdEh1Yi9Db3Vyc18yMDIwX1VuaUdFL0NvdXJzX0dlbmV2ZV82IikKYGBgCgpKZSBjaGFyZ2UgbGVzIHBhY2thZ2VzIHV0aWxlcwoKYGBge3J9CmlmKCFyZXF1aXJlKCJzdHlsbyIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJzdHlsbyIpCiAgbGlicmFyeShzdHlsbykKfQppZighcmVxdWlyZSgiY2x1c3RlciIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJjbHVzdGVyIikKICBsaWJyYXJ5KGNsdXN0ZXIpCn0KaWYoIXJlcXVpcmUoIkZhY3RvTWluZVIiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiRmFjdG9NaW5lUiIpCiAgbGlicmFyeShGYWN0b01pbmVSKQp9CmlmKCFyZXF1aXJlKCJmYWN0b2V4dHJhIikpewogIGluc3RhbGwucGFja2FnZXMoImZhY3RvZXh0cmEiKQogIGxpYnJhcnkoZmFjdG9leHRyYSkKfQppZighcmVxdWlyZSgic21hY29mIikpewogIGluc3RhbGwucGFja2FnZXMoInNtYWNvZiIpCiAgbGlicmFyeShzbWFjb2YpCn0KYGBgCgojIE1hIHByZW1pw6hyZSBDQUggZGVwdWlzIDAKClVuZSBjbGFzc2lmaWNhdGlvbiBhc2NlbmRhbnRlIGhpw6lyYXJjaGlxdWUuCgpgYGB7cn0KIyBKZSBjcsOpZSB1biB0YWJsZWF1IGF2ZWMgZGVzIGRvbm7DqWVzIGF1IGhhc2FyZAp0YWJsZWF1IDwtIGNiaW5kKHg9YygxLDIsNSw4LDExKSx5PWMoMiw0LDEsMTIsMTApKQpjb2xuYW1lcyh0YWJsZWF1KTwtIGMoIkF2b2lyIiwgIsOKdHJlIikKcm93bmFtZXModGFibGVhdSkgPC0gYygiUmFjaW5lIiwiTW9sacOocmUiLCJSYWNpbmUiLCJTY2Fycm9uIiwiQ29ybmVpbGxlIikKdGFibGVhdQpgYGAKCkNhbGN1bCBkZSBkaXN0YW5jZSwgcGFyIGTDqWZhdXQgZXVjaWRpZW5uZS4gb24gcGV1dCB1dGlsaXNlciBsZSBwYXJhbcOodHJlIG1ldGhvZD0ibWFuaGF0dGFuIiBwb3VyIHVuZSBhdXRyZSBkaXN0YW5jZQoKYGBge3J9CmRpc3RhbmNlX3RhYmxlYXUgPC0gZGlzdCh0YWJsZWF1KQpkaXN0YW5jZV90YWJsZWF1CmBgYAoKQ2xhc3NpZmljYXRpb24vY2x1c3RlcmluZy4gUGFyIGTDqWZhdXQgY29tcGxldGUgbGlua2FnZS4gT24gcGV1dCB1dGlsaXNlciBsZSBwYXJhbcOodHJlIG1ldGhvZD0iY2VudHJvaWQiIG91ICJ3YXJkLkQiCmBgYHtyLHJlc3VsdHM9J2hvbGQnfQpoYyA8LSBoY2x1c3QoZGlzdGFuY2VfdGFibGVhdSkKcGxvdChoYykKI09uIGN1c3RvbWlzZSBsZSBncmFwaApwbG90KGhjLCBtYWluPSJNYSBwcmVtacOocmUgY2xhc3NpZmljYXRpb24iLCAgeGxhYj0iRXVjbGlkaWFuIGRpc3RhbmNlLCBcbiBDb21wbGV0ZS1saW5rYWdlIGNsdXN0ZXJpbmciICwgc3ViPSIiICwgaGFuZyA9LTEpCmBgYAoKT24gcGV1dCBmYWlyZSBzb2ktbcOqbWUgdW4gZGVsdGEgZGUgQnVycm93cy4gUG91ciBjZWxhIGplIHotc2NvcmUgbWVzIGRvbm7DqWVzCgpgYGB7cn0KI0plIHRyYW5zZm9ybWUgbW9uIHRhYmxlYXUgZW4gZGF0YS5mcmFtZQp0YWJsZWF1X2RmPC1hcy5kYXRhLmZyYW1lKHRhYmxlYXUpCiNKZSB6LXNjb3JlIG1lcyBkZXV4IGNvbG9ubmVzIGF2ZWMgbGEgZm9uY3Rpb24gc2NhbGUoKQp0YWJsZWF1X3pfYTwtc2NhbGUodGFibGVhdV9kZiRBdm9pcikKdGFibGVhdV96X2I8LXNjYWxlKHRhYmxlYXVfZGYkw4p0cmUpCiNKZSByZWZvcm1lIHVuIHRhYmxlYXUsIGF2ZWMgbGVzIG5vdXZlbGxlcyBkb25uw6llcwp0YWJsZWF1X3o8LWNiaW5kKHRhYmxlYXVfel9hLHRhYmxlYXVfel9iKQp0YWJsZWF1X3oKYGBgCgpFdCBqZSBwcm9kdWlzIHVuIG5vdXZlYXUgZ3JhcGgKCmBgYHtyfQpoYyA8LSBoY2x1c3QoZGlzdGFuY2VfdGFibGVhdSwgbWV0aG9kPSJ3YXJkLkQiKQpwbG90KGhjLCBtYWluPSJNYSBwcmVtacOocmUgY2xhc3NpZmljYXRvaW4iLCAgeGxhYj0iQnVycm93cyBEZWx0YSwgXG4gV2FyZCdzIG1ldGhvZCIgLCBzdWI9IiIgLCBoYW5nID0tMSkKYGBgCgojIFVuIHByZW1pZXIgY2FzCgpKZSBjaGFyZ2UgbW9uIGNvcnB1cwoKYGBge3J9CkJPWUVSX0FNT1VSU0pVUElURVJTRU1FTEVfMTY2NjwtcGFzdGUoc2NhbigiY29ycHVzL0JPWUVSX0FNT1VSU0pVUElURVJTRU1FTEVfMTY2Ni50eHQiLCB3aGF0PSJjaGFyYWN0ZXIiLCBzZXA9IiIpLGNvbGxhcHNlPSIgIikKQk9ZRVJfQVJJU1RPREVNRV8xNjQ4PC1wYXN0ZShzY2FuKCJjb3JwdXMvQk9ZRVJfQVJJU1RPREVNRV8xNjQ4LnR4dCIsIHdoYXQ9ImNoYXJhY3RlciIsIHNlcD0iIiksY29sbGFwc2U9IiAiKQpDT1JORUlMTEVQX0FORFJPTUVERV8xNjUxPC1wYXN0ZShzY2FuKCJjb3JwdXMvQ09STkVJTExFUF9BTkRST01FREVfMTY1MS50eHQiLCB3aGF0PSJjaGFyYWN0ZXIiLCBzZXA9IiIpLGNvbGxhcHNlPSIgIikKQ09STkVJTExFUF9NRURFRV8xNjgyPC1wYXN0ZShzY2FuKCJjb3JwdXMvQ09STkVJTExFUF9NRURFRV8xNjgyLnR4dCIsIHdoYXQ9ImNoYXJhY3RlciIsIHNlcD0iIiksY29sbGFwc2U9IiAiKQpEVVJZRVJfRFlOQU1JU18xNjUzPC1wYXN0ZShzY2FuKCJjb3JwdXMvRFVSWUVSX0RZTkFNSVNfMTY1My50eHQiLCB3aGF0PSJjaGFyYWN0ZXIiLCBzZXA9IiIpLGNvbGxhcHNlPSIgIikKRFVSWUVSX0NMSVRPUEhPTjwtcGFzdGUoc2NhbigiY29ycHVzL0RVUllFUl9DTElUT1BIT05fMTYzMi50eHQiLCB3aGF0PSJjaGFyYWN0ZXIiLCBzZXA9IiIpLGNvbGxhcHNlPSIgIikKTU9MSUVSRV9BTVBISVRSWU9OXzE2Njg8LXBhc3RlKHNjYW4oImNvcnB1cy9NT0xJRVJFX0FNUEhJVFJZT05fMTY2OC50eHQiLCB3aGF0PSJjaGFyYWN0ZXIiLCBzZXA9IiIpLGNvbGxhcHNlPSIgIikKTU9MSUVSRV9NSVNBTlRIUk9QRV8xNjY3PC1wYXN0ZShzY2FuKCJjb3JwdXMvTU9MSUVSRV9NSVNBTlRIUk9QRV8xNjY3LnR4dCIsIHdoYXQ9ImNoYXJhY3RlciIsIHNlcD0iIiksY29sbGFwc2U9IiAiKQpSQUNJTkVfUEhFRFJFXzE2OTc8LXBhc3RlKHNjYW4oImNvcnB1cy9SQUNJTkVfUEhFRFJFXzE2OTcudHh0Iiwgd2hhdD0iY2hhcmFjdGVyIiwgc2VwPSIiKSxjb2xsYXBzZT0iICIpClJBQ0lORV9FU1RIRVJfMTY4OTwtcGFzdGUoc2NhbigiY29ycHVzL1JBQ0lORV9FU1RIRVJfMTY4OS50eHQiLCB3aGF0PSJjaGFyYWN0ZXIiLCBzZXA9IiIpLGNvbGxhcHNlPSIgIikKU0NBUlJPTl9ET01KQVBIRVREQVJNRU5JRV8xNjUzPC1wYXN0ZShzY2FuKCJjb3JwdXMvU0NBUlJPTl9ET01KQVBIRVREQVJNRU5JRV8xNjUzLnR4dCIsIHdoYXQ9ImNoYXJhY3RlciIsIHNlcD0iIiksY29sbGFwc2U9IiAiKQpTQ0FSUk9OX0VDT0xJRVJERVNBTEFNQU5RVUVfMTY1NTwtcGFzdGUoc2NhbigiY29ycHVzL1NDQVJST05fRUNPTElFUkRFU0FMQU1BTlFVRV8xNjU1LnR4dCIsIHdoYXQ9ImNoYXJhY3RlciIsIHNlcD0iIiksY29sbGFwc2U9IiAiKQpST1RST1VfSEVSQ1VMRU1PVVJBTlRfMTYzNjwtcGFzdGUoc2NhbigiY29ycHVzL1JPVFJPVV9IRVJDVUxFTU9VUkFOVF8xNjM2LnR4dCIsIHdoYXQ9ImNoYXJhY3RlciIsIHNlcD0iIiksY29sbGFwc2U9IiAiKQpST1RST1VfRE9NQkVSTkFSRERFQ0FCUkVSRV8xNjQ3PC1wYXN0ZShzY2FuKCJjb3JwdXMvUk9UUk9VX0RPTUJFUk5BUkRERUNBQlJFUkVfMTY0Ny50eHQiLCB3aGF0PSJjaGFyYWN0ZXIiLCBzZXA9IiIpLGNvbGxhcHNlPSIgIikKU0NVREVSWV9NT1JUREVDRVNBUl8xNjM3PC1wYXN0ZShzY2FuKCJjb3JwdXMvU0NVREVSWV9NT1JUREVDRVNBUl8xNjM3LnR4dCIsIHdoYXQ9ImNoYXJhY3RlciIsIHNlcD0iIiksY29sbGFwc2U9IiAiKQpTQ1VERVJZX09SQU5URV8xNjM2PC1wYXN0ZShzY2FuKCJjb3JwdXMvU0NVREVSWV9PUkFOVEVfMTYzNi50eHQiLCB3aGF0PSJjaGFyYWN0ZXIiLCBzZXA9IiIpLGNvbGxhcHNlPSIgIikKI0plIHBldXggcmVnYXJkZXIgaWNpIHVuIGV4ZW1wbGUgKGVubGV2ZXIgbGUgIyBhdSBkw6lidXQgZGUgbGEgbGlnbmUpCiNDT1JORUlMTEVQX0FORFJPTUVERV8xNjUxCmBgYAoKSidlbiBmYWlzIHVuIHRhYmxlYXUKCmBgYHtyfQojSmUgY3LDqWUgdW5lIGxpc3RlIGRlIG1lcyB0ZXh0ZXMKbXkuY29ycHVzLnJhdyA9IGxpc3QoQk9ZRVJfQU1PVVJTSlVQSVRFUlNFTUVMRV8xNjY2LAogICAgICAgICAgICAgICAgICAgICBCT1lFUl9BUklTVE9ERU1FXzE2NDgsCiAgICAgICAgICAgICAgICAgICAgIERVUllFUl9EWU5BTUlTXzE2NTMsCiAgICAgICAgICAgICAgICAgICAgIERVUllFUl9DTElUT1BIT04sCiAgICAgICAgICAgICAgICAgICAgIE1PTElFUkVfQU1QSElUUllPTl8xNjY4LAogICAgICAgICAgICAgICAgICAgICBNT0xJRVJFX01JU0FOVEhST1BFXzE2NjcsCiAgICAgICAgICAgICAgICAgICAgIFNDQVJST05fRE9NSkFQSEVUREFSTUVOSUVfMTY1MywKICAgICAgICAgICAgICAgICAgICAgU0NBUlJPTl9FQ09MSUVSREVTQUxBTUFOUVVFXzE2NTUpCiNKZSB0b2tlbmlzZSBtb24gdGV4dGUgKGVzcGFjZSwgYXBvc3Ryb3BoZSwgcmV0cmFpdCBkZSBsYSBwb25jdHVhdGlvbikKbXkuY29ycHVzLmNsZWFuID0gbGFwcGx5KG15LmNvcnB1cy5yYXcsIHR4dC50by53b3JkcykKI0plIGNvbXB0ZSBsYSBmcsOpcXVlbmNlIGRlcyB0b2tlbnMKY29tcGxldGUud29yZC5saXN0ID0gbWFrZS5mcmVxdWVuY3kubGlzdChteS5jb3JwdXMuY2xlYW4pCiNKZSB0cmFuc2Zvcm1lIGxlIHLDqXN1bHRhdCBlbiB0YWJsZSwgb8O5IGxlcyByw6lzdWx0YXRzIGRlIGNoYXF1ZSBtb3QvdGV4dGUgc29udCBhbGlnbsOpcwp0YWJsZS5vZi5mcmVxdWVuY2llcz1tYWtlLnRhYmxlLm9mLmZyZXF1ZW5jaWVzKG15LmNvcnB1cy5jbGVhbiwgY29tcGxldGUud29yZC5saXN0LCByZWxhdGl2ZSA9IEYpCiNKZSBkb25uZSB1biBub20gw6AgbWVzIGNvbG9ubmVzCnJvdy5uYW1lcyh0YWJsZS5vZi5mcmVxdWVuY2llcyk9YygiYm95ZXJfYWdhbWVtbm9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJib3llcl9hbW91cnNKdXBpdGVyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJkdXJ5ZXJfZHluYW1pcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZHVyeWVyX2NsaXRvcGhvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibW9saWVyZV9hbXBoeXRyaW9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJtb2xpZXJlX21pc2FudGhyb3BlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzY2Fycm9uX2RvbUphcGhldCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAic2NhcnJvbl9lY29saWVyU2FsYW1hbnF1ZSIpCiNKZSBzYXV2ZWdhcmRlIHVuZSBjb3BpZQp3cml0ZS5jc3YodGFibGUub2YuZnJlcXVlbmNpZXMsIGZpbGUgPSAidGFibGUub2YuZnJlcXVlbmNpZXMuY3N2Iixyb3cubmFtZXM9VFJVRSkKI2plIGNvbnZlcnRpcyBtZXMgZG9ubsOpZXMgZW4gZGF0YWZyYW1lCnRhYmxlLm9mLmZyZXF1ZW5jaWVzID0gYXMuZGF0YS5mcmFtZShyZWFkLmNzdihmaWxlPSJ0YWJsZS5vZi5mcmVxdWVuY2llcy5jc3YiLCBzZXAgPSAiLCIsIGhlYWRlciA9IFRSVUUsIHJvdy5uYW1lcz0xLCBxdW90ZSA9ICdcIicpKQojSidhZmZpY2hlIGxlIHLDqXN1bHRhdAojVmlldyh0YWJsZS5vZi5mcmVxdWVuY2llcykKYGBgCgpDZSByw6lzdWx0YXQgbmUgbSdhcnJhbmdlIHBhczogamUgdmFpcyBhdm9pciBiZXNvaW4gZCdhdm9pciBsZXMgb2NjdXJyZW5jZXMgZW4gcmFuZywgZXQgbGVzIHRleHRlcyBlbiBjb2xvbm5lLCBldCBkb25jIGQnaW52ZXJzZXIgbW9uIHRhYmxlYXUKCmBgYHtyfQojaid1dGlsaXNlIGxhIGZvbmN0aW9uIHRyYW5zcG9zZSg/KQp0YWJsZS5vZi5mcmVxdWVuY2llczwtdCh0YWJsZS5vZi5mcmVxdWVuY2llcykKI1ZpZXcodGFibGUub2YuZnJlcXVlbmNpZXMpCmBgYAoKSidvYnNlcnZlIGxhIGRpc3RyaWJ1dGlvbiBkZSBtb24gY29ycHVzLiAKCmBgYHtyfQpzdW1tYXJ5KHRhYmxlLm9mLmZyZXF1ZW5jaWVzKQpgYGAKCkplIHBldXggc3ludGjDqXRpc2VyIGNlIHLDqXN1bHRhdCBhdmVjIHVuZSBib8OudGUgw6AgbW91c3RhY2hlIGRlcyBmcsOpcXVlbmNlcyAoYWJzb2x1ZXMpIGRlcyBtb3RzIGR1IGNvcnB1cy4KCiFbMTAwJSBjZW50ZXJdKGltYWdlcy9ib3gtcGxvdDIucG5nKQoKU291cmNlOiBbc3RhdDRkZWNpc2lvbl0oaHR0cHM6Ly93d3cuc3RhdDRkZWNpc2lvbi5jb20vZnIvbGUtYm94LXBsb3Qtb3UtbGEtZmFtZXVzZS1ib2l0ZS1hLW1vdXN0YWNoZS8pCgpgYGB7cn0KbWFCb2l0ZSA8LSBib3hwbG90KGNvbFN1bXModGFibGUub2YuZnJlcXVlbmNpZXMpLCBtYWluID0gIkRpc3RyaWJ1dGlvbiBkdSBub21icmUgZGUgbW90cyBwYXIgZGUgdGV4dGUiLCB5bGFiPSJOb21icmUgZGUgbW90cyAoZnLDqXEuIGFic29sdWVzKSIsIHN1Yj0iQ29ycHVzOiBkaXN0cmlidXRpb24iKQpgYGAKCkplIGNvbnRyw7RsZSBsJ2jDqXTDqXJvZ8OpbsOpaXTDqSBkZSBtb24gY29ycHVzCgpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTMsIGRwaT0xMH0KI0plIGRvaXMgcmVncm91cGVyIGxlcyBkb25uw6llcyBwYXIgYXV0ZXVyCkJPWUVSID0gY29sU3Vtcyh0YWJsZS5vZi5mcmVxdWVuY2llc1ssZ3JlcGwoJ2JveWVyXycsIGNvbG5hbWVzKHRhYmxlLm9mLmZyZXF1ZW5jaWVzKSldKQpDT1JORUlMTEUgPSBjb2xTdW1zKHRhYmxlLm9mLmZyZXF1ZW5jaWVzWyxncmVwbCgnY29ybmVpbGxlXycsIGNvbG5hbWVzKHRhYmxlLm9mLmZyZXF1ZW5jaWVzKSldKQpEVVJZRVIgPSBjb2xTdW1zKHRhYmxlLm9mLmZyZXF1ZW5jaWVzWyxncmVwbCgnZHVyeWVyXycsIGNvbG5hbWVzKHRhYmxlLm9mLmZyZXF1ZW5jaWVzKSldKQpNT0xJRVJFID0gY29sU3Vtcyh0YWJsZS5vZi5mcmVxdWVuY2llc1ssZ3JlcGwoJ21vbGllcmVfJywgY29sbmFtZXModGFibGUub2YuZnJlcXVlbmNpZXMpKV0pClJBQ0lORSA9IGNvbFN1bXModGFibGUub2YuZnJlcXVlbmNpZXNbLGdyZXBsKCdyYWNpbmVfJywgY29sbmFtZXModGFibGUub2YuZnJlcXVlbmNpZXMpKV0pClNDQVJST04gPSBjb2xTdW1zKHRhYmxlLm9mLmZyZXF1ZW5jaWVzWyxncmVwbCgnc2NhcnJvbl8nLCBjb2xuYW1lcyh0YWJsZS5vZi5mcmVxdWVuY2llcykpXSkKUk9UUk9VID0gY29sU3Vtcyh0YWJsZS5vZi5mcmVxdWVuY2llc1ssZ3JlcGwoJ3JvdHJvdV8nLCBjb2xuYW1lcyh0YWJsZS5vZi5mcmVxdWVuY2llcykpXSkKI0V0IGxlIGdyYXBoaXF1ZQpib3hwbG90KGxpc3QoQk9ZRVIsTU9MSUVSRSwgQ09STkVJTExFLERVUllFUixSQUNJTkUsU0NBUlJPTiwgUk9UUk9VKSwgbmFtZXM9YygnQk9ZRVInLCdNT0xJRVJFJywgJ0NPUk5FSUxMRScsJ0RVUllFUicsJ1JBQ0lORScsJ1NDQVJST04nLCdST1RST1UnKSwgbWFpbj0iTG9uZ3VldXIgZGVzIHRleHRlcyIsIHN1Yj0iQ29ycHVzOiBkaXN0cmlidXRpb24iLHlsYWI9Ik5vbWJyZSBkZSBtb3RzIChmcsOpcXVlbmNlcyBhYnNvbHVlcykiKQpgYGAKCkplIHRyYW5zZm9ybWUgY2V0dGUgdGFibGUgZGVzIGZyw6lxdWVuY2VzIGFic29sdWVzIGVuIHVuZSB0YWJsZSBkZXMgZnLDqXF1ZW5jZXMgcmVsYXRpdmVzCgpgYGB7cn0KI0plIGZhaXMgdW5lIGNvcGllIGRlIG1hIHRhYmxlIGRlIGZyw6lxdWVuY2VzCmZyZXFzX3JlbCA9IHRhYmxlLm9mLmZyZXF1ZW5jaWVzCiNEYW5zIGNldHRlIGNvcGllLCBwb3VyIGNoYWN1biBkZXMgbW90cyBkZSBjaGFxdWUgY29sb25uZSwgamUgZGl2aXNlIGxlIGNoaWZmcmUgdHJvdXbDqSBwYXIgbGEgc29tbWUgZGUgbGEgY29sb25uZQpmb3IoaSBpbiAxOm5jb2woZnJlcXNfcmVsKSl7CiAgICAJZnJlcXNfcmVsWyxpXSA9IGZyZXFzX3JlbFssaV0vc3VtKGZyZXFzX3JlbFssaV0pCn0KaGVhZChmcmVxc19yZWwpCmBgYAoKSmUgcGV1eCAoZXQgbcOqbWUgamUgZG9pcykgc8OpbGVjdGlvbm5lciBkYW5zIGNldHRlIGxpc3RlIGxlcyBuIHBsdXMgZnLDqXF1ZW50cwoKYGBge3J9CiNKZSBwcmVuZHMgbGVzIHByZW1pZXJzIHJhbmdzLiBDaGFuZ2V6IGNlIGNoaWZmcmUgcG91ciBjaGFuZ2VyIGxlcyByw6lzdWx0YXRzIHBhciBsYSBzdWl0ZQpmcmVxc19yZWxfbWZ3ID0gZnJlcXNfcmVsWzE6MTAwLF0KYGBgCgojIEFuYWx5c2UKCiMjIExhIENBSAoKUmVnYXJkb25zIMOgIHF1b2kgcmVzc2VtYmxlIG5vdHJlIENBSAoKYGBge3J9CkNBSCA9IGFnbmVzKGFzLmRpc3QoZGlzdC53dXJ6YnVyZyh0KGZyZXFzX3JlbF9tZncpKSksIG1ldGhvZD0id2FyZCIpCkNBSF9vcmlnX2FnZ2xvQ29lZmYgPC0gQ0FIJGFjCnBsb3QoQ0FIKQpgYGAKCk5ldHRveW9ucyB0b3V0IGNlbGHigKYgCgpgYGB7cn0KcGxvdChDQUgsCiAgICAgbWFpbj0iQ2x1c3RlciBhbmFseXNpcyIsCiAgICAgeGxhYj1wYXN0ZSgiQ29zaW5lIERlbHRhXG4gQWdnbG9tZXJhdGl2ZSBjb2VmZmljaWVudCA9IiwgQ0FIX29yaWdfYWdnbG9Db2VmZiksCiAgICAgaGFuZyA9IC0wLjEpCgpgYGAKCkZhaXNvbnMgdW4gcGV1IG1pZXV4CgpgYGB7cn0KI29uIHZhIGNvbG9yaWVyIGxlcyB2ZWN0ZXVycyBlbiBmb25jdGlvbiBkZSBsJ2F1dGV1ciAoZW4gcHJhdGlxdWUsIGxhIGNoYcOubmUgZGUgY2FyYWN0w6hyZSBhdmFudCBsJ191bmRlcnNjb3JlXykKbGFiZWxzX2NvbG9yID0gdmVjdG9yKGxlbmd0aCA9IGxlbmd0aChDQUgkb3JkZXIubGFiKSkKbGFiZWxzX2NvbG9yW2dyZXAoImJveWVyIiwgQ0FIJG9yZGVyLmxhYildID0gImRhcmtibHVlIgpsYWJlbHNfY29sb3JbZ3JlcCgiZHVyeWVyIiwgQ0FIJG9yZGVyLmxhYildID0gImRhcmtncmV5IgpsYWJlbHNfY29sb3JbZ3JlcCgic2NhcnJvbiIsIENBSCRvcmRlci5sYWIpXSA9ICJkYXJrcmVkIgpsYWJlbHNfY29sb3JbZ3JlcCgibW9saWVyZSIsIENBSCRvcmRlci5sYWIpXSA9ICJkYXJrZ3JlZW4iCgojamUgcmVwcmVuZHMgbW9uIGdyYXBoCnBsb3QoQ0FIKQojSmUgZmFpcyB1biBwZXUgZGUgY29sb3JpYWdlCmZhY3RvZXh0cmE6OmZ2aXpfZGVuZChDQUgsCiAgICAgICAgICAgICAgICAgICAgICB5bGFiPXBhc3RlKCJDb3NpbmUgRGVsdGFcbiBBZ2dsb21lcmF0aXZlIGNvZWZmaWNpZW50ID0iLCByb3VuZChDQUhfb3JpZ19hZ2dsb0NvZWZmLCBkaWdpdD0zKSksCiAgICAgICAgICAgICAgICAgICAgICBrPTIsICMgamUgY2hlcmNoZSDDoCBkw6lnYWdlciBkZXV4IGNsdXN0ZXJzCiAgICAgICAgICAgICAgICAgICAgICByZWN0ID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgIGtfY29sb3JzID0gcmVwKCJibGFjayIsMiksICNqZSBnYXJkZSBsYSBjb3VsZXVyIGRlcyB0cmFpdHMgZW4gbm9pcgogICAgICAgICAgICAgICAgICAgICAgbGFiZWxzX3RyYWNrX2hlaWdodCA9IDAuNywgIyBKZSBnw6hyZSBsYSB0YWlsbGUgZGUgbW9uIHJlY3RhbmdsZQogICAgICAgICAgICAgICAgICAgICAgbGFiZWxfY29scyA9IGxhYmVsc19jb2xvciwgI2onYXBwbGlxdWUgbWVzIGNvdWxldXJzCiAgICAgICAgICAgICAgICAgICAgICBob3Jpej1UKSAjIGplIG1ldHMgbW9uIGdyYXBoIMOgIGwnaG9yaXpvbnRhbCwgcGFyY2UgcXVlIGMnZXN0IHBsdXMgcHJhdGlxdWUKYGBgCgpDJ2VzdCBiaWVuIG1pZXV4LCBtYWlzIHBhcyBlbmNvcmUgcGFyZmFpdDogbGUgY29kZSBlc3QgZXN0IHRyb3AgbG9uZywgcGFzIHRyw6hzIHByb3ByZS4gQ2FjaG9ucyB0b3V0IGNlbGEgZGFucyB1biBjb2luLiBQb3VyIGNlbGEsIGNvbXBsaXF1b25zLW5vdXMgbGUgdHJhdmFpbCB1biBwZXUgcGx1cyBlbiBjcsOpYW50IHVuZSBmb25jdGlvbiBqZSBxdWUgY29uc2VydmUgZGFucyB1biBhdXRyZSBmaWNoaWVyIGFwcGVsw6kgYGZ1bmN0aW9ucy5SYC4KCkplIHZhaXMgZG9uYyBjaGFyZ2VyIGNldHRlIGZvbmN0aW9uIGF2YW50IGRlIGwndXRpbGlzZXIuCgpgYGB7cn0Kc291cmNlKCJmdW5jdGlvbnMuUiIpCmBgYAoKUXVhdHJlIHBhcmFtw6h0cmVzIG9udCDDqXTDqSBwcsOpdnVzCiogTGUgbm9tIGRlcyBkb25uw6llcwoqIExhIGzDqWdlbmRlIMOgIGFmZmljaGVyCiogTGUgY29lZmZpY2hpZW50IGQnYWdnbG9tw6lyYXRpb24gKHLDqWN1cMOpcsOpIGF1IHBhc3NhZ2UsIHVuIHBldSBwbHVzIGhhdXQpCiogdW5lIHZhcmlhYmxlIHBvdXIgbGUgcGFyYW3DqHRyZSBgbGFiZWxzX3RyYWNrX2hlaWdodGAKCmBgYHtyfQpjdXN0b21QbG90KENBSCwiT3JpZ2luYWwgdGV4dHMsIDEwMCBNRlcsIEN1bGxlZCBAIDAlLCBEaXN0YW5jZTogd3VyemJ1cmciLENBSF9vcmlnX2FnZ2xvQ29lZmYsMC43KQpgYGAKCkplIHBldXggZG9uYyByZXByb2R1aXJlIG1hIENBSCBzaW1wbGVtZW50IGF2ZWMgZCdhdXRyZXMgY29uZmlndXJhdGlvbnMuIFRlbnRvbnMgZMOpc29ybWFpcyBhdmVjIHBsdXMgZGUgX01GV18KCmBgYHtyfQojb24gcGFzc2UgZGUgMTAwIMOgIDEwMDAKZnJlcXNfcmVsX21mdyA9IGZyZXFzX3JlbFsxOjEwMDAsXQojamUgcmVjYWxjdWxlIG1hIENBSApDQUggPSBhZ25lcyhhcy5kaXN0KGRpc3Qud3VyemJ1cmcodChmcmVxc19yZWxfbWZ3KSkpLCBtZXRob2Q9IndhcmQiKQpDQUhfb3JpZ19hZ2dsb0NvZWZmIDwtIENBSCRhYwojRXQgZMOpc29ybWFpcywgamUgbidhaSBwbHVzIHF1J3VuZSBzaW1wbGUgZm9uY3Rpb24gIm1haXNvbiIgcG91ciBtZSBwcm9kdWlyZSBtYSB2aXN1YWxpc2F0aW9uCmN1c3RvbVBsb3QoQ0FILCJPcmlnaW5hbCB0ZXh0cywgMTAwMCBNRlcsIEN1bGxlZCBAIDAlLCBEaXN0YW5jZTogd3VyemJ1cmciLENBSF9vcmlnX2FnZ2xvQ29lZmYsMC41KQpgYGAKClRlbnRvbnMgYXZlYyB1bmUgYXV0cmUgZGlzdGFuY2U6IF9SdXppY2thIG1lYXN1cmVfIG91IF9NaW5tYXhfIChjZi5Lb3BwZWwsIE0uIGFuZCBXaW50ZXIsIFkuICgyMDE0KS4gRGV0ZXJtaW5pbmcgaWYgdHdvIGRvY3VtZW50cyBhcmUgd3JpdHRlbiBieSB0aGUgc2FtZSBhdXRob3IuIF9Kb3VybmFsIG9mIHRoZSBBc3NvY2lhdGlvbiBmb3IgSW5mb3JtYXRpb24gU2NpZW5jZSBhbmQgVGVjaG5vbG9neV8sIDY1OiAxNzjigJM4NykgZG9udCBsYSByb2J1c3Rlc3NlIGEgw6l0w6kgZMOpbW9udHLDqWUgcGFyIEtlc3Rtb250IGV0IGFsLiAoIEtlc3RlbW9udCwgTS5TdG92ZXIsIEouS29wcGVsLCBNLkthcnNkb3JwLCBLLkRhZWxlbWFucywgVy4sICBBdXRob3JzaGlwIFZlcmlmaWNhdGlvbiB3aXRoIHRoZSBSdXppY2thIE1ldHJpYywgREggMjAxNiwgSnVsIDIwMTYsIEtyYWtvdyAoUG9sYW5kKSkuCgohWzEwMCUgY2VudGVyXShpbWFnZXMvbWlubWF4LnBuZykKCihfdGZfIHBvdXIgX3Rlcm0gZnJlcXVlbmN5XykKCmBgYHtyfQojamUgcmVjYWxjdWxlIG1hIENBSApDQUggPSBhZ25lcyhhcy5kaXN0KGRpc3QubWlubWF4KHQoZnJlcXNfcmVsX21mdykpKSwgbWV0aG9kPSJ3YXJkIikKQ0FIX29yaWdfYWdnbG9Db2VmZiA8LSBDQUgkYWMKI0V0IGTDqXNvcm1haXMsIGplIG4nYWkgcGx1cyBxdSd1bmUgc2ltcGxlIGZvbmN0aW9uICJtYWlzb24iIHBvdXIgbWUgcHJvZHVpcmUgbWEgdmlzdWFsaXNhdGlvbgpjdXN0b21QbG90KENBSCwiT3JpZ2luYWwgdGV4dHMsIDEwMDAgTUZXLCBDdWxsZWQgQCAwJSwgRGlzdGFuY2U6IE1pbm1heCIsQ0FIX29yaWdfYWdnbG9Db2VmZiwtMC4yKQpgYGAKCkV0IGVuY29yZSB1bmUgYXV0cmUsIGVuIGRpbWludWFudCBkZSBub3V2ZWF1IGxlcyBfTUZXXwoKYGBge3J9CiNvbiByZXBhc3NlIGRlIDEwMDAgw6AgMTAwCmZyZXFzX3JlbF9tZncgPSBmcmVxc19yZWxbMToxMDAsXQojamUgcmVjYWxjdWxlIG1hIENBSApDQUggPSBhZ25lcyhhcy5kaXN0KGRpc3Qud3VyemJ1cmcodChmcmVxc19yZWxfbWZ3KSkpLCBtZXRob2Q9IndhcmQiKQpDQUhfb3JpZ19hZ2dsb0NvZWZmIDwtIENBSCRhYwpjdXN0b21QbG90KENBSCwiT3JpZ2luYWwgdGV4dHMsIDEwMCBNRlcsIEN1bGxlZCBAIDAlLCBEaXN0YW5jZTogd3VyemJ1cmciLENBSF9vcmlnX2FnZ2xvQ29lZmYsMC43KQpgYGAKCiMjIF9VbmRlciB0aGUgaG9vZF86IGxlcyBjbGFzc2VzCgpUb3V0IGNlbGEgZXN0IHRyw6hzIGJpZW4uIE1haXMgcXUnZXN0LWNlIHF1aSBlc3QgY2FjaMOpIGRlcnJpw6hyZSBjZXMgY2x1c3RlcnM/IE5vdXMgcG91dm9ucyBsZSBzYXZvaXIuIFByZW5vbnMgbm90cmUgZGVybmnDqHJlIENBSCwgZXQgcmVnYXJkb25zIGxlIHByb2Nlc3N1cyBkZSBjb25zdHJ1Y3Rpb24gZGVzIGNsdXN0ZXJzOgoKYGBge3J9CkNBSF8yID0gYXMuaGNsdXN0KENBSCkKcGxvdChDQUhfMiRoZWlnaHQsIHR5cGU9ImgiLCB5bGFiPSJoZWlnaHQiKQpgYGAKCkplIHJlbWFycXVlIHF1ZSBqJ2FpIHBsdXNpZXVycyBjbHVzdGVycy4gVGVudG9ucyBkZSB2b2lyIGNlIHF1J2lscyBjb250aWVubmVudC4gQ2xhc3NvbnMgcGFyIDQgbm90cmUgY29ycHVzOgoKYGBge3IscmVzdWx0cz0naG9sZCd9CiNKZSAiY291cGUgZW4gZGV1eCIgbWEgQ0FILCBvdSBwbHV0w7R0IGplIGRlbWFuZGUgdW5lIGNsYXNzaWZpY2F0aW9uIGVuIGRldXggZGVzIGRpZmbDqXJlbnRzCmNsYXNzZXMgPSBjdXRyZWUoQ0FILCBrID0gIjQiKQojSidham91dGUgbWVzIGNsYXNzZXMgYXV4IGZyw6lxdWVuY2VzIHF1ZSBqJ2FpIHV0aWxpc8OpZXMKUHJvY2Vzc2VkX2NsYXNzZXMgPSB0KGZyZXFzX3JlbF9tZncpClByb2Nlc3NlZF9jbGFzc2VzID0gY2JpbmQoYXMuZGF0YS5mcmFtZShQcm9jZXNzZWRfY2xhc3NlcyksIGFzLmZhY3RvcihjbGFzc2VzKSkKY29sbmFtZXMoUHJvY2Vzc2VkX2NsYXNzZXMpWzEwMV0gPSAiQ2xhc3NlcyIKCiNKJ2FmZmljaGUgbGUgcsOpc3VsdGF0ClByb2Nlc3NlZF9jbGFzc2VzWzEwMV0KYGBgCgpEaW1pbnVvbnMgw6AgZGV1eCBsZSBub21icmUgZGUgY2x1c3RlcgoKYGBge3IscmVzdWx0cz0naG9sZCd9CiNKZSAiY291cGUgZW4gZGV1eCIgbWEgQ0FILCBvdSBwbHV0w7R0IGplIGRlbWFuZGUgdW5lIGNsYXNzaWZpY2F0aW9uIGVuIGRldXggZGVzIGRpZmbDqXJlbnRzCmNsYXNzZXMgPSBjdXRyZWUoQ0FILCBrID0gIjIiKQojSidham91dGUgbWVzIGNsYXNzZXMgYXV4IGZyw6lxdWVuY2VzIHF1ZSBqJ2FpIHV0aWxpc8OpZXMKUHJvY2Vzc2VkX2NsYXNzZXMgPSB0KGZyZXFzX3JlbF9tZncpClByb2Nlc3NlZF9jbGFzc2VzID0gY2JpbmQoYXMuZGF0YS5mcmFtZShQcm9jZXNzZWRfY2xhc3NlcyksIGFzLmZhY3RvcihjbGFzc2VzKSkKY29sbmFtZXMoUHJvY2Vzc2VkX2NsYXNzZXMpWzEwMV0gPSAiQ2xhc3NlcyIKCiNKJ2FmZmljaGUgbGUgcsOpc3VsdGF0ClByb2Nlc3NlZF9jbGFzc2VzWzEwMV0KYGBgCgpKZSBwZXV4IGF1c3NpIHJlZ2FyZGVyIGxlcyB2YWxldXJzIGFzc29jacOpZXMgw6AgY2hhY3VuIGRlcyBtb3RzLCBldCBjZXV4IGFzc29jacOpcyDDoCBjaGFxdWUgY2x1c3RlcjoKCmBgYHtyfQpteUNsYXNzZXMgPSBjYXRkZXMoUHJvY2Vzc2VkX2NsYXNzZXMsIG51bS52YXIgPSAxMDEpCm15Q2xhc3NlcwpgYGAKCiMjIF9VbmRlciB0aGUgaG9vZF86IGwnZXMgY2xhc3NlcydBQ1AKCkplIGNvbW1lbmNlIHBhciBmYWlyZSB1bmUgQUNQLiAKCmBgYHtyfQpBQ1AgPSBQQ0EodChmcmVxc19yZWxfbWZ3KSkKYGBgCgpGYWlzb25zIHVuIHBldSBkZSBtw6luYWdlLCBham91dG9ucyBkZSBsJ2luZm9ybWF0aW9uLCBldCB0ZW50b25zIGRlIGNvbXByZW5kcmUKCmBgYHtyfQojY2F0ZWdvcmllcwpnZXRfY2F0ZWdvcmllcyA9IHZlY3RvcihsZW5ndGggPSBsZW5ndGgoY29sbmFtZXMoZnJlcXNfcmVsX21mdykpKQpnZXRfY2F0ZWdvcmllc1tncmVwKCJyYWNpbmUiLCBjb2xuYW1lcyhmcmVxc19yZWxfbWZ3KSldID0gIlJhY2luZSIKZ2V0X2NhdGVnb3JpZXNbZ3JlcCgibW9saWVyZSIsIGNvbG5hbWVzKGZyZXFzX3JlbF9tZncpKV0gPSAiTW9saWVyZSIKZ2V0X2NhdGVnb3JpZXNbZ3JlcCgiZHVyeWVyIiwgY29sbmFtZXMoZnJlcXNfcmVsX21mdykpXSA9ICJEdXJ5ZXIiCmdldF9jYXRlZ29yaWVzW2dyZXAoImJveWVyIiwgY29sbmFtZXMoZnJlcXNfcmVsX21mdykpXSA9ICJCb3llciIKCkFDUCA9IFBDQSh0KGZyZXFzX3JlbF9tZncpKQpmdml6X3BjYV9pbmQoQUNQLCBjb2wuaW5kID0gZ2V0X2NhdGVnb3JpZXMsIGxlZ2VuZD0ibm9uZSIpCmBgYAoKT24gdm9pdCBuZXR0ZW1lbnQgZGV1eCBheGVzLCBxdSBkb2l2ZW50IGJpZW4gcydhcHB1eWVyIHN1ciBxdWVscXVlIGNob3NlLiBTZXJhaXQtaWwgcG9zc2libGUgZGUgZmFpcmUgcmVzc29ydGlyIGxlcyBtb3RzIHF1aSBzb250IMOgIGwnb3JpZ2luZSBkZSBjZSBwYXJ0aXRpb25uZW1lbnQgZGVzIGRvbm7DqWVzPwoKQ29udGludW9ucyBub3RyZSBuZXR0b3lhZ2UgcG91ciB5IHZvaXIgcGx1cyBjbGFpcgoKYGBge3J9CiNjYXRlZ29yaWVzCmdldF9jYXRlZ29yaWVzID0gdmVjdG9yKGxlbmd0aCA9IGxlbmd0aChjb2xuYW1lcyhmcmVxc19yZWxfbWZ3KSkpCmdldF9jYXRlZ29yaWVzW2dyZXAoInJhY2luZSIsIGNvbG5hbWVzKGZyZXFzX3JlbF9tZncpKV0gPSAiUmFjaW5lIgpnZXRfY2F0ZWdvcmllc1tncmVwKCJtb2xpZXJlIiwgY29sbmFtZXMoZnJlcXNfcmVsX21mdykpXSA9ICJNb2xpZXJlIgoKQUNQID0gUENBKHQoZnJlcXNfcmVsX21mdykpCmZ2aXpfcGNhX2luZChBQ1AsIGNvbC5pbmQgPSBnZXRfY2F0ZWdvcmllcywgbGVnZW5kPSJub25lIikKZnZpel9wY2FfdmFyKEFDUCwgY29sLnZhcj0iY29udHJpYiIsZ2VvbS52YXIgPSAidGV4dCIsIHNlbGVjdC52YXIgPSBsaXN0KGNvbnRyaWIgPTIwKSkrCnNjYWxlX2NvbG9yX2dyYWRpZW50MigKICAgICAgICAgICAgICAgICAgICAgIGxvdz0ieWVsbG93IiwgbWlkPSJvcmFuZ2UiLCAKICAgICAgICAgICAgICAgICAgICAgIGhpZ2g9ImRhcmtyZWQiLCBtaWRwb2ludD0xLjYpK3RoZW1lX2J3KCkKYGBgCgpOb3VzIG5vdXMgcmFwcGVsb25zIHF1J2lsIGV4aXN0ZSB1biBwb3VyY2VudGFnZSBkZSByw6lhbGl0w6kgcmV0ZW51IHBhciBheGUsIHF1ZSBub3VzIHBvdXZvbnMgb2JzZXJ2ZXIsIGV0IGRvbmMgY2FsY3VsZXIuIERhbnMgY2UgY2FzIHByw6ljaXMsIGlsIHkgZW4gYSBzZXVsZW1lbnQgdHJvaXMgYXhlcywgY2FyIG5vdXMgbidhdm9ucyBtaXMgcXVlIHF1YXRyZSBkZSB0ZXh0ZXMuCgpgYGB7cn0KQUNQJGVpZwpgYGAKClVuIHBldGl0IGdyYXBoIG1vbnRyZSBiaWVuIGxhIGxlbnRlIHBlcnRlIGRlIHNpZ25pZmljYXRpdml0w6kgZGVzIGF4ZXMKOgpgYGB7cn0KYmFycGxvdChBQ1AkZWlnWywxXSwgbWFpbj0iUGVyY2VudGFnZSBvZiB2YXJpYW5jZSIsIG5hbWVzLmFyZz0xOm5yb3coQUNQJGVpZykpCmBgYAoKIyBMZXMgYXV0cmVzIGFsZ29yaXRobWVzCgojIyB0LVNORQpBdmVjIGzigJlhbGdvcml0aG1lIGRlIEJhcm5lcy1IdXQsCgpgYGB7cn0KbGlicmFyeShSdHNuZSkKbWFSdHNuZSA9IFJ0c25lKHQoZnJlcXNfcmVsX21mdyksIGRpbXMgPSAyLCBpbml0aWFsX2RpbXMgPSAzNiwgcGVycGxleGl0eSA9IDAuNSwgdGhldGEgPSAwLjAsIGNoZWNrX2R1cGxpY2F0ZXMgPSBUUlVFLCBwY2EgPSBUUlVFKQpwbG90KG1hUnRzbmUkWSkKdGV4dChtYVJ0c25lJFlbLDFdLCBtYVJ0c25lJFlbLDJdLCBsYWJlbHMgPSByb3cubmFtZXModChmcmVxc19yZWxfbWZ3KSksIGNleD0uNikgCmBgYAoKQXR0ZW50aW9uISBTaSBvbiByZWxhbmNlIGxhIG3Dqm1lIGNvbW1hbmRlLCBvbiBvYnRpZW50IHVuIHLDqXN1bHRhdCBkaWZmw6lyZW50IQoKYGBge3J9CmxpYnJhcnkoUnRzbmUpCm1hUnRzbmUgPSBSdHNuZSh0KGZyZXFzX3JlbF9tZncpLCBkaW1zID0gMiwgaW5pdGlhbF9kaW1zID0gMzYsIHBlcnBsZXhpdHkgPSAwLjUsIHRoZXRhID0gMC4wLCBjaGVja19kdXBsaWNhdGVzID0gVFJVRSwgcGNhID0gVFJVRSkKcGxvdChtYVJ0c25lJFkpCnRleHQobWFSdHNuZSRZWywxXSwgbWFSdHNuZSRZWywyXSwgbGFiZWxzID0gcm93Lm5hbWVzKHQoZnJlcXNfcmVsX21mdykpLCBjZXg9LjYpIAojZW5jb3JlIHVuZSBmb2lzCmxpYnJhcnkoUnRzbmUpCm1hUnRzbmUgPSBSdHNuZSh0KGZyZXFzX3JlbF9tZncpLCBkaW1zID0gMiwgaW5pdGlhbF9kaW1zID0gMzYsIHBlcnBsZXhpdHkgPSAwLjUsIHRoZXRhID0gMC4wLCBjaGVja19kdXBsaWNhdGVzID0gVFJVRSwgcGNhID0gVFJVRSkKcGxvdChtYVJ0c25lJFkpCnRleHQobWFSdHNuZSRZWywxXSwgbWFSdHNuZSRZWywyXSwgbGFiZWxzID0gcm93Lm5hbWVzKHQoZnJlcXNfcmVsX21mdykpLCBjZXg9LjYpIAojZW5jb3JlIHVuZSBmb2lzCmxpYnJhcnkoUnRzbmUpCm1hUnRzbmUgPSBSdHNuZSh0KGZyZXFzX3JlbF9tZncpLCBkaW1zID0gMiwgaW5pdGlhbF9kaW1zID0gMzYsIHBlcnBsZXhpdHkgPSAwLjUsIHRoZXRhID0gMC4wLCBjaGVja19kdXBsaWNhdGVzID0gVFJVRSwgcGNhID0gVFJVRSkKcGxvdChtYVJ0c25lJFkpCnRleHQobWFSdHNuZSRZWywxXSwgbWFSdHNuZSRZWywyXSwgbGFiZWxzID0gcm93Lm5hbWVzKHQoZnJlcXNfcmVsX21mdykpLCBjZXg9LjYpIApgYGAKCgpPbiBwZXV0IGZhaXJlIHZhcmllciBsYSBwZXJwbMOpeGl0w6kgKGMnZXN0IMOgIGRpcmUgcydhdXRvcmlzZXIgdW5lIGRpc3RvcnNpb24gcGx1cyBncmFuZGUpOiBvbiBwYXNzZSDDoCAxLjUKCmBgYHtyfQptYVJ0c25lID0gUnRzbmUodChmcmVxc19yZWxfbWZ3KSwgZGltcyA9IDIsIGluaXRpYWxfZGltcyA9IDM2LCBwZXJwbGV4aXR5ID0gMS41LCB0aGV0YSA9IDAuMCwgY2hlY2tfZHVwbGljYXRlcyA9IFRSVUUsIHBjYSA9IFRSVUUpCnBsb3QobWFSdHNuZSRZKQp0ZXh0KG1hUnRzbmUkWVssMV0sIG1hUnRzbmUkWVssMl0sIGxhYmVscyA9IHJvdy5uYW1lcyh0KGZyZXFzX3JlbF9tZncpKSwgY2V4PS42KSAKI0VuY29yZSB1bmUgZm9pcwptYVJ0c25lID0gUnRzbmUodChmcmVxc19yZWxfbWZ3KSwgZGltcyA9IDIsIGluaXRpYWxfZGltcyA9IDM2LCBwZXJwbGV4aXR5ID0gMS41LCB0aGV0YSA9IDAuMCwgY2hlY2tfZHVwbGljYXRlcyA9IFRSVUUsIHBjYSA9IFRSVUUpCnBsb3QobWFSdHNuZSRZKQp0ZXh0KG1hUnRzbmUkWVssMV0sIG1hUnRzbmUkWVssMl0sIGxhYmVscyA9IHJvdy5uYW1lcyh0KGZyZXFzX3JlbF9tZncpKSwgY2V4PS42KSAKI0VuY29yZSB1bmUgZm9pcwptYVJ0c25lID0gUnRzbmUodChmcmVxc19yZWxfbWZ3KSwgZGltcyA9IDIsIGluaXRpYWxfZGltcyA9IDM2LCBwZXJwbGV4aXR5ID0gMS41LCB0aGV0YSA9IDAuMCwgY2hlY2tfZHVwbGljYXRlcyA9IFRSVUUsIHBjYSA9IFRSVUUpCnBsb3QobWFSdHNuZSRZKQp0ZXh0KG1hUnRzbmUkWVssMV0sIG1hUnRzbmUkWVssMl0sIGxhYmVscyA9IHJvdy5uYW1lcyh0KGZyZXFzX3JlbF9tZncpKSwgY2V4PS42KSAKI0VuY29yZSB1bmUgZm9pcwptYVJ0c25lID0gUnRzbmUodChmcmVxc19yZWxfbWZ3KSwgZGltcyA9IDIsIGluaXRpYWxfZGltcyA9IDM2LCBwZXJwbGV4aXR5ID0gMS41LCB0aGV0YSA9IDAuMCwgY2hlY2tfZHVwbGljYXRlcyA9IFRSVUUsIHBjYSA9IFRSVUUpCnBsb3QobWFSdHNuZSRZKQp0ZXh0KG1hUnRzbmUkWVssMV0sIG1hUnRzbmUkWVssMl0sIGxhYmVscyA9IHJvdy5uYW1lcyh0KGZyZXFzX3JlbF9tZncpKSwgY2V4PS42KSAKYGBgCgpPbiBwZXV0IGZhaXJlIHZhcmllciBsYSBwZXJwbMOpeGl0w6k6IG9uIHBhc3NlIMOgIDIKCmBgYHtyfQptYVJ0c25lID0gUnRzbmUodChmcmVxc19yZWxfbWZ3KSwgZGltcyA9IDIsIGluaXRpYWxfZGltcyA9IDM2LCBwZXJwbGV4aXR5ID0gMiwgdGhldGEgPSAwLjAsIGNoZWNrX2R1cGxpY2F0ZXMgPSBUUlVFLCBwY2EgPSBUUlVFKQpwbG90KG1hUnRzbmUkWSkKdGV4dChtYVJ0c25lJFlbLDFdLCBtYVJ0c25lJFlbLDJdLCBsYWJlbHMgPSByb3cubmFtZXModChmcmVxc19yZWxfbWZ3KSksIGNleD0uNikgCiNFbmNvcmUgdW5lIGZvaXMKbWFSdHNuZSA9IFJ0c25lKHQoZnJlcXNfcmVsX21mdyksIGRpbXMgPSAyLCBpbml0aWFsX2RpbXMgPSAzNiwgcGVycGxleGl0eSA9IDIsIHRoZXRhID0gMC4wLCBjaGVja19kdXBsaWNhdGVzID0gVFJVRSwgcGNhID0gVFJVRSkKcGxvdChtYVJ0c25lJFkpCnRleHQobWFSdHNuZSRZWywxXSwgbWFSdHNuZSRZWywyXSwgbGFiZWxzID0gcm93Lm5hbWVzKHQoZnJlcXNfcmVsX21mdykpLCBjZXg9LjYpIAojRW5jb3JlIHVuZSBmb2lzCm1hUnRzbmUgPSBSdHNuZSh0KGZyZXFzX3JlbF9tZncpLCBkaW1zID0gMiwgaW5pdGlhbF9kaW1zID0gMzYsIHBlcnBsZXhpdHkgPSAyLCB0aGV0YSA9IDAuMCwgY2hlY2tfZHVwbGljYXRlcyA9IFRSVUUsIHBjYSA9IFRSVUUpCnBsb3QobWFSdHNuZSRZKQp0ZXh0KG1hUnRzbmUkWVssMV0sIG1hUnRzbmUkWVssMl0sIGxhYmVscyA9IHJvdy5uYW1lcyh0KGZyZXFzX3JlbF9tZncpKSwgY2V4PS42KSAKI0VuY29yZSB1bmUgZm9pcwptYVJ0c25lID0gUnRzbmUodChmcmVxc19yZWxfbWZ3KSwgZGltcyA9IDIsIGluaXRpYWxfZGltcyA9IDM2LCBwZXJwbGV4aXR5ID0gMiwgdGhldGEgPSAwLjAsIGNoZWNrX2R1cGxpY2F0ZXMgPSBUUlVFLCBwY2EgPSBUUlVFKQpwbG90KG1hUnRzbmUkWSkKdGV4dChtYVJ0c25lJFlbLDFdLCBtYVJ0c25lJFlbLDJdLCBsYWJlbHMgPSByb3cubmFtZXModChmcmVxc19yZWxfbWZ3KSksIGNleD0uNikgCmBgYAoKIyMgTURTCgpMZSBfTXVsdGlkaW1lbnNpb25hbCBzY2FsaW5nXyBlc3QgdW5lIGNvbXByZXNzaW9uIGRlcyBkb25uw6llcyBlbiBkZXV4IGRpbWVuc2lvbnMuIElsIGVuIGV4aXN0ZSB0cm9pcyB0eXBlczoKKiBjbGFzc2lxdWUKKiBtw6l0cmlxdWUKKiBub24gbcOpdHJpcXVlCgojIyMgTURTIGNsYXNzaXF1ZSAKCkxlIF9NRFNfIGNsYXNzaXF1ZSBzZSBiYXNlIHN1ciBsZSBjYWxjdWwgZGUgZGlzdGFuY2UsIGRvbnQgaWwgdmEgZXNzYXllciBkZSBwcsOpc2VydmVyIGwnZXNzZW50aWVsLgoKYGBge3J9CiNKZSBjcsOpZSBtZXMgZG9ubsOpZXMKZml0ID0gY21kc2NhbGUoZGlzdCh0KGZyZXFzX3JlbF9tZncpLCBtZXRob2QgPSAibWFuaGF0dGFuIiksIGVpZz1UUlVFLCBrPTIpICMgayBlc3QgbGUgbm9tYnJlIGRlIGRpbWVuc2lvbnMgc291aGFpdMOpCiNKZSBkZXNzaW5lIG1vbiByw6lzdWx0YXQKeCA9IGZpdCRwb2ludHNbLDFdCnkgPSBmaXQkcG9pbnRzWywyXQpwbG90KHgsIHksIHhsYWI9cGFzdGUoIkNvb3JkaW5hdGUgMSAoR09GOiAiLCByb3VuZChmaXQkR09GWzFdICogMTAwLCBkaWdpdHM9MiksICIlKSIpLCB5bGFiPXBhc3RlKCJDb29yZGluYXRlIDIgKEdPRjogIiwgcm91bmQoZml0JEdPRlsyXSAqIDEwMCwgZGlnaXRzPTIpLCAiJSkiKSwgbWFpbj0iUE1EIG3DqXRyaXF1ZSIpCnRleHQoeCwgeSwgbGFiZWxzID0gcm93Lm5hbWVzKHQoZnJlcXNfcmVsX21mdykpLCBjZXg9LjcpIApgYGAKCgojIyMgTURTIE3DqXRyaXF1ZQoKTGUgTURTIG3DqXRyaXF1ZSwgZGl0IGF1c3NpIG9yZGluYWwsIG5lIHMnaW50w6lyZXNzZSBwYXMgw6AgbGEgbWVzdXJlIGRlIGRpc3RhbmNlLCBtYWlzIHNhIHZhbGV1ciBlbiByZWxhdGlvbiBhdmVjIGxlcyBhdXRyZXMgcGFpcmVzCgpSYXBwZWxvbnMgcXVlIGxlIHN0cmVzcyBwZXJtZXQgZGUgbWVzdXJlciBsYSBkaXN0b3J0aW9uIGludHJvZHVpdGUgZGFucyBsZSByw6lzdWx0YXQuCgpgYGB7cn0KI0onYWZmaWNoZSBsZSBzdHJlc3MgYXZlYyBsZSBwYXJhbcOodHJlIHZlcmJvc2U9VChSVUUpCk1EU21ldHJpcXVlID0gbWRzKGRpc3QodChmcmVxc19yZWxfbWZ3KSwgbWV0aG9kID0gIm1hbmhhdHRhbiIpLCBuZGltPTIsIHR5cGU9Im9yZGluYWwiLHZlcmJvc2U9VCkKYGBgCgpgYGB7cn0KcGxvdChNRFNtZXRyaXF1ZSwgc3ViPXBhc3RlKCJTdHJlc3MsICIsIHJvdW5kKE1EU21ldHJpcXVlJHN0cmVzcywgZGlnaXRzPTIpKSkKYGBgCgojIyMgTm9uIG3DqXRyaXF1ZQoKYGBge3J9CiNKJ2FmZmljaGUgbGUgc3RyZXNzIGF2ZWMgbGUgcGFyYW3DqHRyZSB2ZXJib3NlPVQoUlVFKQpNRFNub25NZXRyaXF1ZSA9IG1kcyhkaXN0KHQoZnJlcXNfcmVsX21mdyksIG1ldGhvZCA9ICJldWNsaWQiKSwgbmRpbT0yLCB0eXBlPSJpbnRlcnZhbCIsdmVyYm9zZT1UKQpgYGAKCmBgYHtyfQpwbG90KE1EU25vbk1ldHJpcXVlLCBzdWI9cGFzdGUoIlN0cmVzcywgIiwgcm91bmQoTURTbWV0cmlxdWUkc3RyZXNzLCBkaWdpdHM9MikpKQpgYGAK